package util

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"
)

func Post(ctx context.Context, url, token string, data []byte, timeout ...time.Duration) (body []byte, err error) {
	if !strings.HasPrefix(url, "http") {
		url = "http://" + url
	}

	client := getClientFromCtx(ctx)
	if len(timeout) > 0 {
		client = &http.Client{
			Timeout:   timeout[0],
			Transport: client.Transport,
		}
	}
	req, err := http.NewRequest("POST", url, bytes.NewReader(data))
	if err != nil {
		//log.Error(err)
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	//if token != "" {
	//	WrapRequestWithToken(req, token)
	//}
	//WrapRequestWithCtx(req, ctx)
	response, err := client.Do(req)

	if err != nil {
		log.Println(err)
		return nil, err
	}
	defer response.Body.Close()

	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	if response.StatusCode >= 300 || response.StatusCode < 200 {
		log.Println("response status code error :", response.StatusCode, "; body:", string(body))
		return nil, errors.New(string(body))
	}
	// log.Println(string(body), response.StatusCode)
	return body, nil
}

func Get3(url string) (body []byte, err error) {
	client := http.DefaultClient
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	response, err := client.Do(req)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	defer response.Body.Close()

	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	return body, nil
}

func Get2(ctx context.Context, url, token string, timeout ...time.Duration) (body []byte, err error) {
	if !strings.HasPrefix(url, "http") {
		url = "http://" + url
	}

	client := getClientFromCtx(ctx)
	if len(timeout) > 0 {
		client = &http.Client{
			Timeout:   timeout[0],
			Transport: client.Transport,
		}
	}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	//if token != "" {
	//	WrapRequestWithToken(req, token)
	//}
	//WrapRequestWithCtx(req, ctx)
	response, err := client.Do(req)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	defer response.Body.Close()

	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	if response.StatusCode >= 300 || response.StatusCode < 200 {
		log.Println("response status code error :", response.StatusCode, "; body:", string(body))
		return nil, errors.New(string(body))
	}
	// log.Println(string(body), response.StatusCode)
	return body, nil
}

func PostRaw(ctx context.Context, url, token string, data []byte, header map[string]string, timeout ...time.Duration) (body []byte, err error) {
	if !strings.HasPrefix(url, "http") {
		url = "http://" + url
	}

	client := getClientFromCtx(ctx)
	if len(timeout) > 0 {
		client = &http.Client{
			Timeout:   timeout[0],
			Transport: client.Transport,
		}
	}
	req, err := http.NewRequest("POST", url, bytes.NewReader(data))
	if err != nil {
		log.Println(err)
		return nil, err
	}

	for k, v := range header {
		req.Header.Set(k, v)
	}
	//if token != "" {
	//	WrapRequestWithToken(req, token)
	//}
	//WrapRequestWithCtx(req, ctx)
	response, err := client.Do(req)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	defer response.Body.Close()

	body, err = ioutil.ReadAll(response.Body)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	if response.StatusCode >= 300 || response.StatusCode < 200 {
		log.Println("response status code error :", response.StatusCode, "; body:", string(body))
		return nil, errors.New(string(body))
	}
	// log.Println(string(body), response.StatusCode)
	return body, nil
}

func PostJson2(ctx context.Context, url, token string, body interface{}, result interface{}, timeout ...time.Duration) error {
	return requestExpectingJson(ctx, http.MethodPost, url, token, body, result, timeout...)
}

func GetJson2(ctx context.Context, url, token string, result interface{}, timeout ...time.Duration) error {
	return requestExpectingJson(ctx, http.MethodGet, url, token, nil, result, timeout...)
}

func DeleteJson(ctx context.Context, url, token string, body interface{}, result interface{}, timeout ...time.Duration) error {
	return requestExpectingJson(ctx, http.MethodDelete, url, token, body, result, timeout...)
}

func PutJson(ctx context.Context, url, token string, body interface{}, result interface{}, timeout ...time.Duration) error {
	return requestExpectingJson(ctx, http.MethodPut, url, token, body, result, timeout...)
}

func requestExpectingJson(ctx context.Context, method, url, token string, body interface{}, result interface{}, timeout ...time.Duration) error {
	client := getClientFromCtx(ctx)
	if len(timeout) > 0 {
		client = &http.Client{
			Timeout:   timeout[0],
			Transport: client.Transport,
		}
	}

	var bodyReader io.Reader
	if body != nil {
		reqbs, err := json.Marshal(body)
		if err != nil {
			return err
		}
		bodyReader = bytes.NewBuffer(reqbs)
	}

	req, err := http.NewRequest(method, url, bodyReader)
	if err != nil {
		return err
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	//if token != "" {
	//	WrapRequestWithToken(req, token)
	//}
	//WrapRequestWithCtx(req, ctx)
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode/100 != 2 {
		err := fmt.Errorf("err, StatusCode = %d", resp.StatusCode)
		return err
	}

	if result != nil {
		respbs, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}
		var resp struct {
			Code    int             `json:"code"`
			Message string          `json:"message"`
			Data    json.RawMessage `json:"data"`
		}
		err = json.Unmarshal(respbs, &resp)
		if err != nil {
			return err
		}
		if resp.Message != "" {
			return errors.New(resp.Message)
		}

		if resp.Data != nil {
			err = json.Unmarshal(resp.Data, result)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

/*
func BetterResponseMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) (err error) {
		var result struct {
			Code    int         `json:"code"`
			Message interface{} `json:"message,omitempty"`
		}
		rawWriter := c.Response().Writer
		recorder := httptest.NewRecorder()
		c.Response().Writer = recorder
		err = next(c)
		c.Response().Writer = rawWriter
		if err == nil {
			for k, vs := range recorder.HeaderMap {
				for _, v := range vs {
					rawWriter.Header().Add(k, v)
				}
			}
			if !c.Response().Committed {
				c.Set(api.ResponseCodeCtxKey, result.Code)
				return c.JSON(http.StatusOK, result)
			}
			if !strings.Contains(rawWriter.Header().Get(echo.HeaderContentType), echo.MIMEApplicationJSON) {
				_, err = io.Copy(rawWriter, recorder.Body)
				return
			}
			rawWriter.Write([]byte(`{"code":0,"data":`))
			io.Copy(rawWriter, recorder.Body)
			_, err = io.Copy(rawWriter, recorder.Body)
			rawWriter.Write([]byte(`}`))
			c.Set(api.ResponseCodeCtxKey, 0)
			return
		}
		if herr, ok := err.(*echo.HTTPError); ok {
			result.Code = herr.Code
			result.Message = herr.Message
		} else {
			result.Code = http.StatusInternalServerError
			result.Message = err.Error()
		}
		c.Set(api.ResponseCodeCtxKey, result.Code)
		return c.JSON(http.StatusOK, result)
	}
}*/

//func SetAPINameMiddleWare(apiName string) func(next echo.HandlerFunc) echo.HandlerFunc {
//	return func(next echo.HandlerFunc) echo.HandlerFunc {
//		return func(context echo.Context) error {
//			context.Set(api.APINameCtxKey, apiName)
//			return next(context)
//		}
//	}
//}

//func WrapRequestWithCtx(req *http.Request, ctx context.Context) {
//	if ns := ctx.Value(proto.HeaderVmrNs); ns != nil {
//		req.Header.Set(proto.HeaderVmrNs, ns.(string))
//	}
//}

//func WrapRequestWithToken(req *http.Request, token string) {
//	req.Header.Set(proto.HeaderVmrToken, token)
//	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
//}

/*
func WrapRequestWithVmrAuth(req *http.Request, vmrAuth proto.VMRAuth) {
	req.Header.Set(proto.HeaderVmrNs, vmrAuth.Namespace)
	req.Header.Set(proto.HeaderVmrRole, vmrAuth.Role)
	req.Header.Set(proto.HeaderVmrDesc, vmrAuth.Desc)
	req.Header.Set(proto.HeaderVmrExpr, strconv.FormatInt(vmrAuth.Expried, 10))
	req.Header.Set(proto.HeaderVmrToken, vmrAuth.OriginToken)
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", vmrAuth.OriginToken))
}
*/
func WrapCtxWithClient(ctx context.Context, httpCli *http.Client) context.Context {
	return context.WithValue(ctx, "_client", httpCli)
}

func getClientFromCtx(ctx context.Context) (httpCli *http.Client) {
	httpCli = http.DefaultClient
	value := ctx.Value("_client")
	if value != nil {
		if cli, ok := value.(*http.Client); ok {
			httpCli = cli
		}
	}
	return
}
